package com.uinnova.product.eam.service.es;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.binary.core.util.BinaryUtils;
import com.uino.bean.cmdb.base.ESCIHistoryInfo;
import com.uino.bean.cmdb.base.ESCIInfo;
import com.uino.bean.permission.base.SysUser;
import com.uino.dao.AbstractESBaseDao;
import com.uino.dao.ESConst;
import com.uino.util.exception.LoginException;
import com.uino.util.sys.BeanUtil;
import com.uino.util.sys.SysUtil;
import org.elasticsearch.action.ActionListener;
import org.elasticsearch.action.bulk.BulkItemResponse;
import org.elasticsearch.action.bulk.BulkRequest;
import org.elasticsearch.action.bulk.BulkResponse;
import org.elasticsearch.action.support.WriteRequest;
import org.elasticsearch.action.update.UpdateRequest;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import java.io.IOException;
import java.math.BigDecimal;
import java.util.*;
import java.util.Map.Entry;
import java.util.stream.Collectors;

/**
 * ES-CI历史
 *
 * @author zoumengjie
 */
@Service
public class IamsESCIHistoryPrivateSvc extends AbstractESBaseDao<ESCIHistoryInfo, JSONObject> {

    private static final Logger log = LoggerFactory.getLogger(IamsESCIHistoryPrivateSvc.class);

    @Override
    public String getIndex() {
        return ESConst.INDEX_CMDB_CI_HISTORY + "_private";
    }

    @Override
    public String getType() {
        return ESConst.INDEX_CMDB_CI_HISTORY + "_private";
    }

    @PostConstruct
    public void init() {
        super.initIndex();
    }

    @Override
    public Long saveOrUpdate(JSONObject obj, boolean isRefresh) {
        Long id = 0L;
        super.fillPreferencesInfo(obj);
        UpdateRequest uRequest = new UpdateRequest(getIndex(), getType(), obj.get("uniqueCode").toString());
        uRequest.doc(obj);
        if (isRefresh) {
            uRequest.setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE);
        }
        uRequest.docAsUpsert(true);
        try {
            getClient().update(uRequest, RequestOptions.DEFAULT);
            id = obj.getLong("id");
        } catch (IOException e) {
            log.error(e.getMessage(), e);
            e.printStackTrace();
        }
        return id;
    }

    @Override
    public Map<String, Object> saveOrUpdateBatchMessage(JSONArray list, Boolean isAsync) {
        Map<String, Object> retMap = new HashMap<String, Object>();
        if (list != null && list.size() > 0) {
            BulkRequest bulkRequest = new BulkRequest();
            SysUser currentUser = null;
            try {
                currentUser = SysUtil.getCurrentUserInfo();
            } catch (LoginException e) {
                log.debug("无法获取当前登陆用户，该持久化信息可能会缺失[创建/修改]人信息和domain域信息不对的问题");
            }
            for (int i = 0; (!list.isEmpty() && i < list.size()); i++) {
                JSONObject obj = list.getJSONObject(i);
                fillPreferencesInfo(obj, currentUser);
                UpdateRequest uRequest = new UpdateRequest(getIndex(), getType(), obj.get("uniqueCode").toString());
                uRequest.doc(obj);
                uRequest.docAsUpsert(true);
                bulkRequest.add(uRequest);
            }
            bulkRequest.timeout(TimeValue.timeValueMinutes(2));
            // bulkRequest.setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE);
            if (isAsync) {
                getClient().bulkAsync(bulkRequest, RequestOptions.DEFAULT, new ActionListener<BulkResponse>() {
                    int fail = 0;
                    int success = 0;

                    @Override
                    public void onResponse(BulkResponse response) {
                        List<String> errMesList = new ArrayList<String>();
                        for (BulkItemResponse item : response.getItems()) {
                            if (item.isFailed()) {
                                fail++;
                                errMesList.add(item.getFailureMessage());
                            } else {
                                success++;
                            }
                        }
                        if (response.hasFailures()) {
                            log.info(response.buildFailureMessage());
                        }
                        retMap.put("failCount", fail);
                        retMap.put("successCount", success);
                        retMap.put("errMessge", errMesList);
                    }

                    @Override
                    public void onFailure(Exception e) {
                        log.error(e.getMessage(), e);
                    }
                });
            } else {
                int fail = 0;
                bulkRequest.setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE);
                try {
                    BulkResponse bulkResponse = getClient().bulk(bulkRequest, RequestOptions.DEFAULT);
                    List<String> errMesList = new ArrayList<String>();
                    for (BulkItemResponse item : bulkResponse.getItems()) {
                        if (item.isFailed()) {
                            fail++;
                            errMesList.add(item.getFailureMessage());
                        }
                    }
                    if (bulkResponse.hasFailures()) {
                        log.info(bulkResponse.buildFailureMessage());
                    }
                    retMap.put("failCount", fail);
                    retMap.put("errMessge", errMesList);
                } catch (Exception e) {
                    log.error(e.getMessage(), e);
                }
            }
        }
        return retMap;
    }

    public Long saveOrUpdateHistoryInfo(ESCIInfo ciInfo, ESCIHistoryInfo.ActionType action) {
        if (action == ESCIHistoryInfo.ActionType.SAVE_OR_UPDATE) {
            Map<String, Long> ciCodeMaxVersion = this.getCICodeMaxVersion(Collections.singletonList(ciInfo.getCiCode()));
            // todo 新增CI与历史库ciCode相同时版本号递增？当ciCode相同，所属分类不同时会有问题，还没想好如何避免
            // 此处修改ciInfo对象，CI保存时版本号已更新，无需维护

            /*
             *   设计库历史表中保存的version字段如果根据设计库的version来 在数据导入导出删除再导入时会混乱
             *   这里还是单独维护一下吧 保证生成的历史数据列表顺序唯一且递增
             *
             * */
            Long version = ciCodeMaxVersion.get(ciInfo.getCiCode());
            if (version != null) {
                log.info("########## 历史库ciCode：【{}】，对应历史库最大版本号：【{}】", ciInfo.getCiCode(), version);
                ciInfo.setVersion(version + 1);
            }

        }
        ESCIHistoryInfo historyInfo = this.buildCIHistoryInfo(ciInfo, action);
        return this.saveOrUpdate(historyInfo);
    }

    public Map<String, Long> saveOrUpdateHistoryInfosBatch(List<ESCIInfo> ciInfos, ESCIHistoryInfo.ActionType action) {
        Map<String, Long> ciCodeVersionMap = new HashMap<>();
        if (!BinaryUtils.isEmpty(ciInfos)) {
            JSONArray historyInfos = new JSONArray();

            // 这里的批量保存和单个保存的version逻辑相同 历史表的version数据自己维护
            List<String> ciCodes = ciInfos.stream().map(ESCIInfo::getCiCode).collect(Collectors.toList());
            Map<String, Long> ciCodeMaxVersion = this.getCICodeMaxVersion(ciCodes);
            for (ESCIInfo ciInfo : ciInfos) {
                if (action == ESCIHistoryInfo.ActionType.SAVE_OR_UPDATE) {
                    // 新增CI与历史库ciCode相同时版本号递增
                    Long version = ciCodeMaxVersion.get(ciInfo.getCiCode());
                    if (version != null) {
                        log.info("########## 历史库ciCode：【{}】，对应历史库最大版本号：【{}】", ciInfo.getCiCode(), version);
                        ciInfo.setVersion(version + 1);
                    }
                }
                ESCIHistoryInfo historyInfo = this.buildCIHistoryInfo(ciInfo, action);
                String jsonStr = JSON.toJSONString(historyInfo);
                JSONObject json = JSON.parseObject(jsonStr);
                historyInfos.add(json);
                ciCodeVersionMap.put(ciInfo.getCiCode(), ciInfo.getVersion());
            }
            // 感觉异步也没啥问题 裂了。。。
            this.saveOrUpdateBatchMessage(historyInfos, true);
        }
        return ciCodeVersionMap;
    }

    private ESCIHistoryInfo buildCIHistoryInfo(ESCIInfo esciInfo, ESCIHistoryInfo.ActionType action) {
        action = action == null ? ESCIHistoryInfo.ActionType.SAVE_OR_UPDATE : action;
        ESCIHistoryInfo historyInfo = ESCIHistoryInfo.builder().uniqueCode(esciInfo.getId() + "_" + esciInfo.getVersion()).version(esciInfo.getVersion()).action(action.getValue()).build();
        BeanUtil.copyProperties(esciInfo, historyInfo, "createTime", "modifyTime");
        Map<String, Object> attrs = new HashMap<String, Object>();
        Iterator<Entry<String, Object>> it = historyInfo.getAttrs().entrySet().iterator();
        while (it.hasNext()) {
            Entry<String, Object> entry = it.next();
            String key = entry.getKey();
            Object val = entry.getValue() == null ? "" : entry.getValue();
            if (val instanceof Number) {
                attrs.put(key, new BigDecimal(val.toString()).toString());
            } else if (val instanceof String) {
                attrs.put(key, val);
            } else {
                attrs.put(key, JSON.toJSONString(val));
            }
        }
        historyInfo.setAttrs(attrs);
        return historyInfo;
    }

    public Map<String, Long> getCICodeMaxVersion(List<String> ciCodes) {
        Map<String, Long> res = new HashMap<>();
        BoolQueryBuilder query = QueryBuilders.boolQuery();
        if(!BinaryUtils.isEmpty(ciCodes)){
            query.must(QueryBuilders.termsQuery("ciCode.keyword", ciCodes));
        }
        Map<String, BigDecimal> rltIdMaxVersionMap = super.groupByFieldMaxVal("ciCode.keyword", "version", query);
        if (rltIdMaxVersionMap != null && rltIdMaxVersionMap.size() > 0) {
            rltIdMaxVersionMap.forEach((key, val) -> res.put(key, val.longValue()));
        }
        return res;
    }

    /**
     *  根据 CI_ID 在私有库CI历史表中创建历史版本数据
     * @param ciInfos CI数据
     * @param idVersionMap CI_ID 与 HISTORY_MAX_VERSION 组成的map
     * @param action 操作类型
     * @return
     */
    public Map<String, Long> saveOrUpdateHistoryInfosBatchById(List<ESCIInfo> ciInfos, Map<Long, Long> idVersionMap, ESCIHistoryInfo.ActionType action) {
        Map<String, Long> ciCodeVersionMap = new HashMap<>();
        if (!BinaryUtils.isEmpty(ciInfos)) {
            JSONArray historyInfos = new JSONArray();
            for (ESCIInfo ciInfo : ciInfos) {
                if (action == ESCIHistoryInfo.ActionType.SAVE_OR_UPDATE) {
                    // 新增CI与历史库ID相同时版本号递增
                    Long version = idVersionMap.get(ciInfo.getId());
                    if (version != null) {
                        log.info("########## 历史库ciCode：【{}】，对应历史库最大版本号：【{}】", ciInfo.getCiCode(), version);
                        ciInfo.setVersion(version);
                    }
                }
                ESCIHistoryInfo historyInfo = this.buildCIHistoryInfo(ciInfo, action);
                String jsonStr = JSON.toJSONString(historyInfo);
                JSONObject json = JSON.parseObject(jsonStr);
                historyInfos.add(json);
                ciCodeVersionMap.put(ciInfo.getCiCode(), ciInfo.getVersion());
            }
            // 感觉异步也没啥问题 裂了。。。
            this.saveOrUpdateBatchMessage(historyInfos, Boolean.FALSE);
        }
        return ciCodeVersionMap;
    }

    /**
     *  根据CI的 ID 和 查询私有库历史表中最大版本的数据版本号
     * @param ciIds CI_ID
     * @return CI_ID 与 HISTORY_MAX_VERSION 组成的map
     */
    public Map<Long, Long> getIdMaxVersion(List<Long> ciIds) {
        Map<Long, Long> res = new HashMap<>();
        BoolQueryBuilder query = QueryBuilders.boolQuery();
        if(!BinaryUtils.isEmpty(ciIds)){
            query.must(QueryBuilders.termsQuery("id", ciIds));
        }
        Map<String, BigDecimal> rltIdMaxVersionMap = super.groupByFieldMaxVal("id", "version", query);
        if (rltIdMaxVersionMap != null && rltIdMaxVersionMap.size() > 0) {
            rltIdMaxVersionMap.forEach((key, val) -> res.put(Long.valueOf(key), val.longValue()));
        }
        return res;
    }

    /**
     *  根据CI的 ID 查询私有库历史表中最大版本的数据
     * @param ciIds CI_ID
     * @return CI_ID 与 CI_INFO 组成的map
     */
    public Map<Long, ESCIHistoryInfo> queryMaxVersionDataByCiId(List<Long> ciIds) {
        // todo optimize
        Map<Long, ESCIHistoryInfo> res = new HashMap<>();
        Map<Long, Long> idMaxVersion = this.getIdMaxVersion(ciIds);
        if (!BinaryUtils.isEmpty(idMaxVersion)) {
            BoolQueryBuilder ciHistoryQuery = QueryBuilders.boolQuery();
            for (Long ciId : idMaxVersion.keySet()) {
                BoolQueryBuilder oneQuery = QueryBuilders.boolQuery();
                oneQuery.must(QueryBuilders.termQuery("version", idMaxVersion.get(ciId)));
                oneQuery.must(QueryBuilders.termQuery("id", ciId));
                ciHistoryQuery.should(oneQuery);
            }
            List<ESCIHistoryInfo> historyCIList = this.getListByQuery(ciHistoryQuery);
            res = historyCIList.stream().collect(Collectors.toMap(ESCIHistoryInfo::getId, e->e, (k1, k2)->k1));
        }
        return res;
    }

}